import numpy as np
import tensorflow as tf
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

tf.set_random_seed(777)

# 利用tensorflow框架和神经网络模型，求解breast_cancer分类问题
# 1.
# 导入必要的包
# 2.
# 利用sklearn导入breast_cancer数据，分割数据集为训练集和测试集
x, y = load_breast_cancer(return_X_y=True)
x = StandardScaler().fit_transform(x)
m, n = x.shape
print(m, n)
y = y.reshape(-1, 1)
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.7, random_state=777)

L1 = n * 2
L2 = int(n * 0.8)
L3 = 1

# 3.
# 定义输入 / 输出的占位符
with tf.name_scope('Input'):
    ph_x = tf.compat.v1.placeholder(dtype=tf.float32, shape=[None, n], name='ph_x')
    ph_y = tf.compat.v1.placeholder(dtype=tf.float32, shape=[None, L3], name='ph_y')

# 4.
# 自定义神经网络模型结构，并初始化相应的参数(W, b)
with tf.name_scope('weight_and_bias'):
    w1 = tf.Variable(tf.random.normal([n, L1]), dtype=tf.float32, name='w1')
    w2 = tf.Variable(tf.random.normal([L1, L2]), dtype=tf.float32, name='w2')
    w3 = tf.Variable(tf.random.normal([L2, L3]), dtype=tf.float32, name='w3')
    b1 = tf.Variable(tf.random.normal([1, L1]), dtype=tf.float32, name='b1')
    b2 = tf.Variable(tf.random.normal([1, L2]), dtype=tf.float32, name='b2')
    b3 = tf.Variable(tf.random.normal([1, L3]), dtype=tf.float32, name='b3')

# 5.
# 利用前向传播，写出预测模型
with tf.name_scope('FP'):
    z1 = tf.matmul(ph_x, w1) + b1
    a1 = tf.sigmoid(z1)
    z2 = tf.matmul(a1, w2) + b2
    a2 = tf.sigmoid(z2)
    z3 = tf.matmul(a2, w3) + b3
    a3 = tf.sigmoid(z3)

# 8.
# 定义交叉熵代价
with tf.name_scope('cost'):
    cost = tf.math.negative(tf.reduce_mean(
        ph_y * tf.math.log(a3)
        + (1 - ph_y) * tf.math.log(1 - a3)
    ), name='cost')

# 6.
# 定义梯度下降优化器，学习率设置为0.01
alpha = 0.01
with tf.name_scope('train'):
    train = tf.compat.v1.train.GradientDescentOptimizer(learning_rate=alpha)\
        .minimize(cost)


# 7.
# 定义准确率
with tf.name_scope('accuracy'):
    acc = tf.reduce_mean(tf.cast(tf.equal(a3 > 0.5, ph_y > 0.5), dtype=tf.float32), name='acc_score')

# 9.
# 创建会话，并初始化全局变量
with tf.compat.v1.Session() as sess:
    sess.run(tf.compat.v1.global_variables_initializer())

    # 10.
    # 运行梯度下降，记录代价，迭代次数为3000，每100次迭代，输出代价，准确率
    iters = 3000
    group = 25
    cost_arr = np.zeros(iters)
    for i in range(iters):
        _, costv, accv = sess.run([train, cost, acc], feed_dict={ph_x: x_train, ph_y: y_train})
        cost_arr[i] = costv
        if i % group == 0:
            print(f'#{i + 1}: cost = {costv}, acc = {accv} [auto bp]')
    if i % group != 0:
        print(f'#{i + 1}: cost = {costv}, acc = {accv}')

    # 11.
    # 画出代价曲线
    spr = 1
    spc = 1
    spn = 0
    plt.figure(figsize=[6, 6])
    plt.plot(cost_arr[:200], label='cost function value auto bp')
    plt.legend()

    # 12.
    # 输出测试集的准确率
    print(f'测试集的准确率:{sess.run(acc, feed_dict={ph_x: x_test, ph_y: y_test})}')

    # Finally show all plotting
    plt.show()
